#!/usr/bin/python
# Copyright 2014 Sean Burford <vk2ete@vk2ete.net>.  All Rights Reserved.

import datetime
import HTMLParser
import logging
import re
import sys
import urllib

import event


class HTMLTagData(object):

  def __init__(self, tag, attrs):
    self.tag = tag
    self.attrs = attrs
    self.data = ''

  def AddData(self, data):
    data = data.strip()
    if not data:
      return
    if self.data:
      self.data = '%s %s' % (self.data, data)
    else:
      self.data = data

  def Attribute(self, name):
    for attr, val in self.attrs:
      if attr == name:
        return val
    return ''

  def __repr__(self):
    return '<HTMLTagData %s %r %r %r>' % (id(self), self.tag, self.attrs,
                                          self.data)


class EasyPHPCalendarParser(HTMLParser.HTMLParser):
  _CAT_RE = re.compile(r'^s[0-9]+$')

  def __init__(self, src):
    HTMLParser.HTMLParser.__init__(self)
    self._src = src
    self._tag_stack = []
    self._date = None
    self.events = [event.CalendarEvent(self._src, '', None, None)]

  def ParseTag(self, tagattr):
    raise NotImplementedError('ParseTag')

  def handle_starttag(self, tag, attrs):
    """Handle an opening tag.  Part of the HTMLParser interface."""
    self._tag_stack.append(HTMLTagData(tag, attrs))

  def handle_endtag(self, tag):
    """Handle a tag closing.  Part of the HTMLParser interface."""
    tagattr = self._tag_stack.pop()
    while tagattr.tag != tag:
      self.ParseTag(tagattr)
      tagattr = self._tag_stack.pop()

    if tagattr.tag.lower() in ('b', 'i', 'strong', 'a',):
      self._tag_stack[-1].AddData('%s' % (tagattr.data,))
    elif tagattr.tag.lower() in ('br',):
      self._tag_stack[-1].AddData('<%s>' % (tagattr.tag,))
    else:
      self.ParseTag(tagattr)

  def handle_data(self, data):
    """Handle a non tag.  Part of the HTMLParser interface."""
    if not self._tag_stack:
      if data.strip():
        logging.warning('data when tag stack empty: %s', data)
    else:
      self._tag_stack[-1].AddData(data)


class EasyPHPCalendarDayParser(EasyPHPCalendarParser):

  def ParseTag(self, tagattr):
    """Update self.events based on tagattr."""

    attr_class = tagattr.Attribute('class').split()
    if not attr_class:
      return

    if 'popupDate' in attr_class and tagattr.data:
      fmt = '%b %d, %Y'
      self._date = datetime.datetime.strptime(tagattr.data, fmt)

    if 'popupEventTitle' in attr_class and tagattr.data:
      self.events.append(event.CalendarEvent(self._src, tagattr.data,
                                             self._date, self._date))
    elif 'categoryName' in attr_class:
      # I'm not sure how to represent these, but the content is captured in
      # selection anyway so no great loss.
      pass
    elif 'categorySelection' in attr_class and tagattr.data:
      self.events[-1].AddCategory(tagattr.data)
    elif 'popupEventTime' in attr_class and tagattr.data:
      self.events[-1].SetTime(tagattr.data)
    elif 'popupEventDescription' in attr_class and tagattr.data:
      self.events[-1].SetDescription(tagattr.data)
    elif 'popupEventDate' in attr_class and tagattr.data:
      # Another free text field I'm not sure how to deal with,
      # eg: 'Single Day Event'
      self.events[-1].SetComment(tagattr.data)
    # else:
    #   logging.debug('%d %r', len(self._tag_stack), tagattr)

    # Some categories are expressed as CSS attributes.
    for category in attr_class:
      if self._CAT_RE.match(category):
        self.events[-1].AddCategory(category)


class EasyPHPCalendarMonthParser(EasyPHPCalendarParser):

  def _PrettyDates(self, dates_str):
    """Parse "May 17th, 2014 (Sat) -- May 18th, 2014 (Sun)" into dates."""

    dates = []
    for date_str in dates_str.split('-'):
      date_str = date_str.strip()
      if not date_str:
        continue
      try:
        month, day, year, _ = date_str.split()
      except ValueError:
        logging.warning('unparsable date: %s',
                        date_str.encode('string_escape'))
        continue
      day = re.sub(r'[^\d]', '', day)
      fmt = '%b %d, %Y'
      date = '%s %s, %s' % (month, day, year)
      parsed = datetime.datetime.strptime(date, fmt)
      dates.append(datetime.datetime(parsed.year, parsed.month, parsed.day))
    return dates

  def ParseTag(self, tagattr):
    """Update self.events based on tagattr."""

    attr_class = tagattr.Attribute('class').split()
    if not attr_class:
      if tagattr.tag.lower() == 'div':
        if tagattr.data.lower().startswith('state: '):
          self.events[-1].AddCategory(tagattr.data.split(' ', 1)[1])
      return

    if 'newDate' in attr_class and tagattr.data:
      dates = self._PrettyDates(tagattr.data)
      if len(dates) == 1:
        self.events.append(event.CalendarEvent(self._src, '',
                                               dates[0], dates[0]))
      elif len(dates) == 2:
        self.events.append(event.CalendarEvent(self._src, '',
                                               dates[0], dates[1]))
      else:
        logging.warning('failed to parse dates: %s', tagattr.data)
        self.events.append(event.CalendarEvent(self._src, '', None, None))
    elif 'newTime' in attr_class and tagattr.data:
      self.events[-1].SetTime(tagattr.data)
    elif 'tableTitle' in attr_class and tagattr.data:
      self.events[-1].SetSubject(tagattr.data)
    elif 'tableDescr' in attr_class and tagattr.data:
      self.events[-1].SetDescription(tagattr.data)
    # else:
    #   logging.debug('%d %r', len(self._tag_stack), tagattr)

    # Some categories are expressed as CSS attributes.
    for category in attr_class:
      if self._CAT_RE.match(category):
        self.events[-1].AddCategory(category)


def main():
  location_clues = (
      event.LocationClue('VK2', 'DURAL',
                         'ARNSW, Quarry Road, Dural, New South Wales, '
                         'Australia'),
      event.LocationClue('VK2', 'WYONG',
                         'Wyong Race Club, Howarth St, Wyong, '
                         'New South Wales, Australia'),
      event.LocationClue('VK3', 'CRANBOURNE',
                         'corner Clarendon and High st, Cranbourne, '
                         'Australia'),
      event.LocationClue('VK3', 'HIGHETT',
                         '33 Turner Rd, Highett, Victoria, Australia'),
      event.LocationClue('VK4', 'REDCLIFFE',
                         'MacFarlane Park in Klingner Rd, Kippa Ring, Qld, '
                         'Australia'),
      event.LocationClue('VK4', 'KIPPA RING',
                         'MacFarlane Park in Klingner Rd, Kippa Ring, Qld, '
                         'Australia'),
      event.LocationClue('VK5', 'ST JOHNS AMBULANCE',
                         '9 Acorn Rd, Camden Park SA, Australia'),
      event.LocationClue('VK6', 'SANDERSON',
                         '5 Sanderson Rd, Lesmurdie, Western Australia, '
                         'Australia'),
  )

  print """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//VK2ETE//NONSGML easyphpcalendar_to_ical v1.0//EN"""

  for url in sys.argv[1:]:
    fp = urllib.urlopen(url)
    # fp = open(url, 'r')
    buf = fp.read()
    fp.close()

    parser = EasyPHPCalendarMonthParser(url)
    buf = buf.decode(errors='replace')
    parser.feed(buf)

    if parser.events:
      for evt in parser.events:
        desc = evt.AsICalendar()
        if desc:
          print desc.encode('ascii', errors='replace')

  print 'END:VCALENDAR'


if __name__ == '__main__':
  main()
